
package com.shiku.imserver.common.ws;


import java.io.UnsupportedEncodingException;
import java.nio.ByteBuffer;
import java.util.List;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.tio.core.ChannelContext;
import org.tio.core.Tio;
import org.tio.core.exception.AioDecodeException;
import org.tio.core.utils.ByteBufferUtils;
import org.tio.websocket.common.Opcode;


public class WsServerDecoder {
    public enum Step {
        header, remain_header, data;

    }


    private static Logger log = LoggerFactory.getLogger(WsServerDecoder.class);


    public static WsRequest decode(ByteBuffer buf, ChannelContext channelContext) throws AioDecodeException {

        WsSessionContext imSessionContext = (WsSessionContext) channelContext.getAttribute();

        List<byte[]> lastParts = imSessionContext.getLastParts();


        int initPosition = buf.position();

        int readableLength = buf.limit() - initPosition;


        int headLength = 2;


        if (readableLength < headLength) {

            return null;

        }


        byte first = buf.get();


        boolean fin = ((first & 0x80) > 0);


        int rsv = (first & 0x70) >>> 4;

        byte opCodeByte = (byte) (first & 0xF);

        Opcode opcode = Opcode.valueOf(opCodeByte);

        if (opcode == Opcode.CLOSE) {
            ;
        }


        if (!fin) {

            log.error("{} 暂时不支持fin为false的请求", channelContext);

            Tio.remove(channelContext, "暂时不支持fin为false的请求");

            return null;

        }


        imSessionContext.setLastParts(null);


        byte second = buf.get();

        boolean hasMask = ((second & 0xFF) >> 7 == 1);


        if (hasMask) {


            headLength += 4;

        }

        int payloadLength = second & Byte.MAX_VALUE;


        byte[] mask = null;

        if (payloadLength == 126) {

            headLength += 2;

            if (readableLength < headLength) {

                return null;

            }

            payloadLength = ByteBufferUtils.readUB2WithBigEdian(buf);


        } else if (payloadLength == 127) {

            headLength += 8;

            if (readableLength < headLength) {

                return null;

            }


            payloadLength = (int) buf.getLong();

            log.info("{} payloadLengthFlag: 127，payloadLength {}", channelContext, Integer.valueOf(payloadLength));

        }


        if (payloadLength < 0 || payloadLength > 524288) {

            throw new AioDecodeException("body length(" + payloadLength + ") is not right");

        }


        if (readableLength < headLength + payloadLength) {

            return null;

        }


        if (hasMask) {

            mask = ByteBufferUtils.readBytes(buf, 4);

        }


        WsRequest websocketPacket = new WsRequest();

        websocketPacket.setWsEof(fin);

        websocketPacket.setWsHasMask(hasMask);

        websocketPacket.setWsMask(mask);

        websocketPacket.setWsOpcode(opcode);

        websocketPacket.setWsBodyLength(payloadLength);


        if (payloadLength == 0) {

            return websocketPacket;

        }


        byte[] array = ByteBufferUtils.readBytes(buf, payloadLength);

        if (hasMask) {

            for (int i = 0; i < array.length; i++) {

                array[i] = (byte) (array[i] ^ mask[i % 4]);

            }

        }


        if (!fin) {


            log.error("payloadLength {}, lastParts size {}, array length {}", new Object[]{Integer.valueOf(payloadLength), Integer.valueOf(lastParts.size()), Integer.valueOf(array.length)});

            return websocketPacket;

        }

        int allLength = array.length;

        if (lastParts != null) {

            for (byte[] part : lastParts) {

                allLength += part.length;

            }

            byte[] allByte = new byte[allLength];


            int offset = 0;

            for (byte[] part : lastParts) {

                System.arraycopy(part, 0, allByte, offset, part.length);

                offset += part.length;

            }

            System.arraycopy(array, 0, allByte, offset, array.length);

            array = allByte;

        }


        websocketPacket.setBytes(array);


        if (opcode != Opcode.BINARY) {


            try {


                String text = null;

                text = new String(array, "utf-8");

                websocketPacket.setWsBodyText(text);

            } catch (UnsupportedEncodingException e) {

                log.error(e.toString(), e);

            }

        }


        return websocketPacket;

    }

}


